﻿Option Strict On
Option Compare Binary

Imports System.IO
Imports Ionic.Zip
Imports <xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
Imports System.Collections.ObjectModel

''' <summary>
''' Represents a Visual Studio Content Installer package
''' </summary>
''' <remarks></remarks>
Public Class VsiPackage

    Dim isStarterKit As Boolean
    Dim isItemTemplate As Boolean

    ''' <summary>
    ''' Gets or sets the End User License Agreement for the package
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property Eula As String
    ''' <summary>
    ''' Gets or sets a list of Visual Studio contents
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks>Notice that the CreateVsiPackage method relies on this property</remarks>
    Public Property Items As ItemsList
    ''' <summary>
    ''' Gets or sets the Content Installer version. 2.0 is the default value
    ''' </summary>
    ''' <value></value>
    ''' <returns></returns>
    ''' <remarks></remarks>
    Public Property ContentInstallerVersion As String = "2.0"

    ''' <summary>
    ''' An event that is raised when a new content is added to the package
    ''' </summary>
    ''' <remarks></remarks>
    Public Event ProgressChanged()

    ''' <summary>
    ''' Applies a digital .Pfx or .Spc certificate to .Vsi packages. 
    ''' </summary>
    ''' <param name="vsiPackage"></param>
    ''' <remarks></remarks>
    Public Shared Sub ApplyDigitalSignature(ByVal vsiPackage As String, ByVal certificateFileName As String, ByVal password As String)

        'Retrieves the IDE's directory. Required to determine the position of MakeZipExe.exe
        If IO.Path.GetExtension(certificateFileName).ToLower = ".pfx" And password Is Nothing Then
            Throw New ArgumentNullException("password", "You specified a .pfx certificate but a password is also required")
        End If

        Dim packagePath As String = Path.GetDirectoryName(vsiPackage)
        Debug.WriteLine(packagePath)

        Try
            'changes the extension from .Vsi to .Zip
            Dim newpath As String = IO.Path.ChangeExtension(vsiPackage, ".zip")
            If IO.File.Exists(newpath) Then IO.File.Delete(newpath)
            My.Computer.FileSystem.RenameFile(vsiPackage, Path.GetFileName(newpath))
            Debug.WriteLine(newpath)

            'Converts the .zip to .exe

            Dim exepath As String = IO.Path.GetDirectoryName(newpath) + "\" + Path.GetFileNameWithoutExtension(newpath) + ".exe"
            Debug.WriteLine(exepath)

            Dim idePath As String = String.Empty, sdkPath As String = String.Empty

            idePath = ContentInstaller.VisualStudio2010InstallDir
            sdkPath = ContentInstaller.DotNet4SdkInstallDir

            Debug.WriteLine(idePath)
            Debug.WriteLine(sdkPath)

            Dim p As Process = Process.Start(idePath + "makezipexe.exe", "-zipfile:" + newpath + " -output:" + exepath)

            'waiting for the operation to complete
            p.WaitForExit()

            'if completed, signs the exe and then re-converts to .Vsi
            If p.HasExited = True Then
                Dim s As Process
                Select Case IO.Path.GetExtension(certificateFileName).ToLower
                    Case Is = ".pfx"
                        s = Process.Start(sdkPath + "bin\signtool.exe", "sign /f " + certificateFileName + " /p " + password + " " + exepath)
                        s.WaitForExit()
                        System.Threading.Thread.Sleep(500)
                    Case Else
                        s = Process.Start(sdkPath + "bin\signtool.exe", "sign /f " + certificateFileName + " " + exepath)
                        s.WaitForExit()
                        System.Threading.Thread.Sleep(500)
                End Select
                If s.ExitCode = 1 Then
                    Throw New InvalidOperationException("Signing failed. Ensure you provided a valid certificate and password. If it fails again, then try to run SignTool manually.")
                End If
            End If

            My.Computer.FileSystem.DeleteFile(newpath)
            My.Computer.FileSystem.RenameFile(exepath, Path.GetFileName(vsiPackage))

        Catch ex As Exception

            Throw
        End Try
    End Sub

    ''' <summary>
    ''' Creates a .Vsi package starting from a set of ItemFile objects. Also allows specifying if an End User License Agreement has to be added
    ''' </summary>
    ''' <param name="outputFile"></param>
    ''' <param name="addEula"></param>
    ''' <remarks></remarks>
    Public Sub CreatePackage(ByVal outputFile As String, ByVal addEula As Boolean)

        Try

            'sores the .Vsi's path
            Dim temp As String = IO.Path.GetTempPath

            Dim contentFileName As String = temp + "\" + IO.Path.GetFileNameWithoutExtension(outputFile)

            'Get the name of the .vscontent file
            Dim vscontentFileFullPath As String = contentFileName + ".vscontent"

            'Concurrently creates a .zip file that handles the .vsi content
            If File.Exists(outputFile) = True Then My.Computer.FileSystem.DeleteFile(outputFile)
            Dim ZippedFile As New ZipFile(outputFile)

            'Declares an XML document that will be used later
            Dim vsDocument As XDocument = Nothing

            'for each item in the collection, adds a tag based on the content type
            For Each item In Me.Items

                If LCase(IO.Path.GetExtension(item.FullPath)) = ".snippet" Then

                    'Determines the language for the current code snippet
                    Dim sn As String = My.Computer.FileSystem.ReadAllText(item.FullPath)

                    If sn.ToUpper.Contains("CODE LANGUAGE=""VB""") Then
                        item.Attributes.Add("lang", "vb")
                        item.DisplayName = Path.GetFileNameWithoutExtension(item.FullPath) + " - Visual Basic Code Snippet"
                        sn = ""
                    ElseIf sn.ToUpper.Contains("CODE LANGUAGE=""CSHARP""") = True Then
                        item.Attributes.Add("lang", "csharp")
                        item.DisplayName = Path.GetFileNameWithoutExtension(item.FullPath) + " - Visual C# Code Snippet"
                        sn = ""
                    ElseIf sn.ToUpper.Contains("CODE LANGUAGE=""FSHARP""") = True Then
                        item.Attributes.Add("lang", "jsharp")
                        item.DisplayName = Path.GetFileNameWithoutExtension(item.FullPath) + " - Visual F# Code Snippet"
                        sn = ""
                    ElseIf sn.ToUpper.Contains("CODE LANGUAGE=""XML""") = True Then
                        item.Attributes.Add("lang", "xml")
                        item.DisplayName = Path.GetFileNameWithoutExtension(item.FullPath) + " - Xml Code Snippet"
                        sn = ""
                    End If

                    item.FileContentType = "Code Snippet"
                    item.FileName = Path.GetFileName(item.FullPath)
                    item.ContentVersion = Me.ContentInstallerVersion

                ElseIf IO.Path.GetExtension(item.FileName).ToLower = ".vsmacros" Then
                    item.FileContentType = "Macro Project"
                    item.FileName = Path.GetFileName(item.FullPath)
                    item.DisplayName = String.Concat(Path.GetFileNameWithoutExtension(item.FileName), " - Macro project")
                    item.Description = Path.GetFileNameWithoutExtension(item.FileName)
                    item.ContentVersion = Me.ContentInstallerVersion

                ElseIf IO.Path.GetExtension(item.FullPath).ToLower = ".addin" Then

                    'indi scrivo il file .vscontent
                    item.FileContentType = "Addin"
                    item.FileName = Path.GetFileName(item.FullPath)
                    item.DisplayName = String.Concat(Path.GetFileNameWithoutExtension(item.FullPath), " - Addin")
                    item.Description = Path.GetFileNameWithoutExtension(item.FullPath)
                    item.ContentVersion = Me.ContentInstallerVersion

                    ZippedFile.AddFile(item.Tag, "")

                End If

                'reads the content of the original file and puts it into the zip
                ZippedFile.AddFile(item.FullPath, "")
                RaiseEvent ProgressChanged()
            Next

            'Creates an XML file by using LINQ, executing a query for all the elements in the file collection
            vsDocument = <?xml version="1.0" encoding="UTF-8"?>
                         <VSContent xmlns="http://schemas.microsoft.com/developer/vscontent/2005">
                             <%= From elemento In Me.Items Select _
                                 <Content>
                                     <FileName><%= elemento.FileName %></FileName>
                                     <FileContentType><%= elemento.FileContentType %></FileContentType>
                                     <DisplayName><%= elemento.DisplayName %></DisplayName>
                                     <Description><%= elemento.Description %></Description>
                                     <ContentVersion><%= elemento.ContentVersion %></ContentVersion>
                                     <Attributes>
                                         <%= From at In elemento.Attributes _
                                             Select _
                                             <Attribute name=<%= at.Key %> value=<%= at.Value %>/> %>
                                     </Attributes>
                                 </Content> %>
                         </VSContent>

            Dim documento As String = vsDocument.ToString
            File.WriteAllText(vscontentFileFullPath, documento)

            'adds the .vscontent file to the zip archive
            ZippedFile.AddFile(vscontentFileFullPath, "")

            'Since .vsi's EULA is nothing but a .zip comment, adds to the zip the EULA string as a comment
            If addEula = True And String.IsNullOrEmpty(Me.Eula) = False Then
                ZippedFile.Comment = Me.Eula
            End If

            ZippedFile.Save()

        Catch ex As PathTooLongException
            Throw New PathTooLongException(ex.Message)
        Catch ex As IO.DirectoryNotFoundException
            Throw New DirectoryNotFoundException(ex.Message)
        Catch ex As IO.DriveNotFoundException
            Throw New DriveNotFoundException(ex.Message)
        Catch ex As IO.InvalidDataException
            Throw New IO.InvalidDataException(ex.Message)
        Catch ex As System.Security.SecurityException
            Throw New System.Security.SecurityException(ex.Message)
        Catch ex As IO.IOException
            Throw New IO.IOException(ex.Message)
        Catch ex As Exception
            Throw New Exception(ex.Message)
        End Try

    End Sub



    Public Sub New(ByVal items As ItemsList)
        Me.Items = items
    End Sub
End Class
